
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections;
using System.Text.RegularExpressions;


using Swaf.BizObj;
using Swaf.Config;

namespace Swaf.BizObj.fieldValidator
{	
	/// <summary> The abstract base class for all FieldValidator objects that
	/// validates an individual field of a <c>BusinessObject</c>.
	/// </summary>
	[Serializable]
	public abstract class FieldValidator
	{		
		/// <summary>
		/// This exception is thrown whenever a String would be an invalid field.
		/// For example, if someone tried to set an Age field to "xyz" or "-1".</summary>
		public class NotValid : BaseException
		{
			/// <summary>Creates a new NotValid exception</summary>
			/// <param name="bizObjType">name of the BusinessObject's type.  E.g., "Address", "Doctor", etc.</param>
			/// <param name="name">the invalid field's name.</param>
			/// <param name="newValue">String representation of the value that wasn't valid.</param>
			/// <param name="details">more details saying why the String wasn't valid.</param>
			public NotValid(string bizObjType, string name, string newValue, string details)
				:base(bizObjType + "." + name + " can't be changed to " + newValue + ": " + details)
			{
				Application.currentApp.Log.println(Application.DEFAULT_FRAMEWORK_LOG_LEVEL,"Throwing " + this);
			}
			public NotValid(string bizObjType, string name, string newValue, string details, Exception innerException)
				:base(bizObjType + "." + name + " can't be changed to " + newValue + ": " + details, innerException)
			{
				Application.currentApp.Log.println(Application.DEFAULT_FRAMEWORK_LOG_LEVEL,"Throwing " + this);
			}

			public NotValid(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
				: base(info, context)
			{
			}
		}
		
		/*
		/// <summary>Validates that <c>value</c> (as a String) meets the constraints of <c>this</c> <c>FieldValidator</c>
		/// for inclusion in the field called <c>name</c> of the containing BusinessObject, <c>bizObj</c>.
		/// Note that <c>value</c> is always a String even though the field's type may not be String
		/// (e.g., if a given field's type is Number, this function validates that the String will
		/// convert to a Number correctly as well as, for example, verifying that the number is in range).
		/// So this function might be called when someone wants to know if it is valid to say, for
		/// example, 'bizObj.putConverted(name, value)'.
		/// </summary>
		/// <returns>Returns silently if <c>value</c> is valid.
		/// @throws NotValid when <c>value</c> is not valid (use 'getMessage()' on the
		/// Exception to get a human readable description of why <c>value</c> is not valid).
		/// 
		/// </returns>
		public void  validate(IBizObj bizObj, string name, string newValue)
		{
			string problem = validateNoThrow(bizObj, name, newValue);
			if (problem != null)
				throw new NotValid(bizObj.type, name, newValue, problem);
		}
		*/
		
		/// <summary>Validates that <c>value</c> (as a String) meets the constraints of <c>this</c> <c>FieldValidator</c>
		/// for inclusion in the field called <c>name</c> of the containing BusinessObject, <c>bizObj</c>.
		/// Note that <c>value</c> is always a String even though the field's type may not be String
		/// (e.g., if a given field's type is Number, this function validates that the String will
		/// convert to a Number correctly as well as, for example, verifying that the number is in range).
		/// So this function might be called when someone wants to know if it is valid to say, for
		/// example, 'bizObj.putConverted(name, value)'.
		/// </summary>
		/// <returns>Returns <c>null</c> if <c>value</c> is valid.
		/// Returns a String describing the problem if <c>value</c> is not valid.
		/// 
		/// </returns>
		public abstract string validateNoThrow(IBizObj bizObj, string name, string newValue);

		public void setPatternMatch(string pattern, Regex regex)
		{
			patternString_ = pattern;
			regexp_ = regex;
		}
		
		/// <summary>Converting a FieldValidator into a String produces the context that identifies that FieldValidator.
		/// E.g., "optional-uppercase-letters", "identifier", "pattern/{1+ {lowercase-letter}}{3-5 {digit}}{0+ {anychar}}foo/", etc.
		/// </summary>
		public override string ToString()
		{
			return context_;
		}
		
		/// <summary>Returns a more detailed description than what is returned from <c>toString()</c>.
		/// The returned string provides details about what this validator is and what its settings are.
		/// </summary>
		/// <param name="prefix">the left margin.  Normally a run of "\t" chars.
		/// 
		/// </param>
		public abstract string details(string prefix);
		
		/// <summary>This is the <em>Pattern</em> used to match this FieldValidator.
		/// Warning: this will likely be <c>null</c> on all objects except the one(s) in <c>prototypes_</c>.
		/// </summary>
		public virtual string pattern
		{get{
			return patternString_;
		}}
		
		/// <summary>This is the compiled <em>Perl5 Regular Expression</em> that corresponds to <c>pattern()</c>.
		/// Warning: this will likely be <c>null</c> on all objects except the one(s) in <c>prototypes_</c>.
		/// </summary>
		public virtual Regex regexp
		{get{
			return regexp_;
		}}
				
		/// <summary>This is a factory method that creates a FieldValidator object.
		/// It's very nearly the Prototype pattern, except under some
		/// circumstances this method does something more than simply
		/// 'clone()' the <c>this</c> object.
		/// </summary>
		/// <param name="context">The string that caused this to get created.
		/// E.g., "optional-uppercase-letters", "identifier", "pattern/{digits}{3-5 {letter}}{identifier}foo/", etc.
		/// </param>
		/// <param name="a">The 1st "group" in the pattern that matched <c>context</c>.
		/// That is, the portion of <c>context</c> that matched the pattern's 1st <c>(...)</c>,
		/// or <c>null</c> if either there was no 1st <c>(...)</c> or that <c>(...)</c> didn't match.
		/// </param>
		/// <param name="b">The 2nd "group" in the pattern that matched <c>context</c>.
		/// That is, the portion of <c>context</c> that matched the pattern's 2nd <c>(...)</c>,
		/// or <c>null</c> if either there was no 2nd <c>(...)</c> or that <c>(...)</c> didn't match.
		/// </param>
		/// <param name="c">The 3rd "group" in the pattern that matched <c>context</c>.
		/// That is, the portion of <c>context</c> that matched the pattern's 3rd <c>(...)</c>,
		/// or <c>null</c> if either there was no 3rd <c>(...)</c> or that <c>(...)</c> didn't match.
		/// 
		/// </param>
		protected internal abstract FieldValidator create(string context, string a, string b, string c);
		
		/// <summary>This is the 'toString()' representation of this FieldValidator.
		/// That is, converting a FieldValidator into a String produces the context.
		/// E.g., "optional-uppercase-letters", "identifier", "pattern/{1+ {lowercase-letter}}{3-5 {digit}}{0+ {anychar}}foo/", etc.
		/// </summary>
		protected internal string context_;
		
		/// <summary>This is the <em>Pattern</em> used to match this FieldValidator.
		/// Warning: this will likely be <c>null</c> on all objects except the one(s) in <c>prototypes_</c>.
		/// </summary>
		private string patternString_ = null;
		
		/// <summary>This is the compiled <em>Perl5 Regular Expression</em> that corresponds to <c>patternString_</c>.
		/// Warning: this will likely be <c>null</c> on all objects except the one(s) in <c>prototypes_</c>.
		/// </summary>
		private Regex regexp_ = null;
		
		/// <summary>Constructor receives the FieldValidator's context.
		/// E.g., "optional-uppercase-letters", "identifier", "pattern/{1+ {lowercase-letter}}{3 {digit}}/", etc.
		/// This String is used in 'toString()'.
		/// </summary>
		protected internal FieldValidator(string context)
		{
			context_ = context;
		}
		
		/*
		/// <summary>Splits the parameter-string into individual parameter-strings
		/// using a "," (optionally preceded and/or followed by spaces).
		/// This is a helper function, normally called from the 'create()' method of a derived class.
		/// <p>For example, if <c>params</c> is "123 , abc,35" this will return 3 strings: ("123", "abc", "35").
		/// </summary>
		/// <param name="context">the original validator string (for error messages).
		/// </param>
		/// <param name="minNumParams">the minimum valid number of parameters.
		/// </param>
		/// <param name="maxNumParams">the maximum valid number of parameters.
		/// Use Integer.MAX_VALUE to mean "you can have as many parameters as you'd like"
		/// @throws ParseError when something goes wrong (e.g., if there are either too few or
		/// too many parameters).
		/// 
		/// </param>
		static protected internal IList splitParams(string context, string callParams, int minNumParams, int maxNumParams)
		{
			IList list = PatternUsage.split(PatternRepository.knownGoodPattern(" *, *"), callParams);
			if (list.Count < minNumParams)
				parseError("not enough parameters: " + context);
			else if (list.Count > maxNumParams)
				parseError("too many parameters: " + context);
			return list;
		}
		*/
		
		/// <summary>Throws a properly formatted <c>FieldValidator.ParseError</c>.
		/// This is a helper function, normally called from the 'create()' method of a derived class.
		/// </summary>
		static protected internal void  parseError(string message)
		{
			throw new ParseError(message);
		}
		
		/// <summary>Converts the parameter-string to a 'long', throwing a FieldValidator.ParseError if it fails.
		/// This is a helper function, normally called from the 'create()' method of a derived class.
		/// </summary>
		/// <param name="context">the original validator string (for error messages).
		/// </param>
		/// <param name="s">the string to be converted.
		/// @throws ParseError when something goes wrong (i.e., if it can't be converted).
		/// 
		/// </param>
		static protected internal long toLong(string context, object s)
		{
			try
			{
				return (long) System.Int64.Parse(s.ToString());
			}
			catch (System.FormatException e)
			{
				parseError("expected an integer; found \"" + s + "\": " + e.Message + ": " + context);
			}
			return 0;
		}
		
		/// <summary>Converts the parameter-string to an 'int', throwing a FieldValidator.ParseError if it fails.
		/// This is a helper function, normally called from the 'create()' method of a derived class.
		/// </summary>
		/// <param name="context">the original validator string (for error messages).
		/// </param>
		/// <param name="s">the string to be converted.
		/// @throws ParseError when something goes wrong (i.e., if it can't be converted).
		/// 
		/// </param>
		static protected internal int toInt(string context, object s)
		{
			try
			{
				return System.Int32.Parse(s.ToString());
			}
			catch (System.FormatException e)
			{
				parseError("expected an integer; found \"" + s + "\": " + e.Message + ": " + context);
			}
			return 0;
		}
		
		/// <summary>Converts the parameter-string to a 'double', throwing a FieldValidator.ParseError if it fails.
		/// This is a helper function, normally called from the 'create()' method of a derived class.
		/// </summary>
		/// <param name="context">the original validator string (for error messages).
		/// </param>
		/// <param name="s">the string to be converted.
		/// @throws ParseError when something goes wrong (i.e., if it can't be converted).
		/// 
		/// </param>
		static protected internal double toDouble(string context, object s)
		{
			try
			{
				return System.Double.Parse(s.ToString());
			}
			catch (System.FormatException e)
			{
				parseError("expected a floating point number; found \"" + s + "\": " + e.Message + ": " + context);
			}
			return 0;
		}
	}
}